#include <stdio.h>
#include <stdlib.h> // atoi
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <ctype.h>

int sockfd; // 定义文件描述符

void set_slave_id(uint8_t *p, int id) // 设置从机id
{
    *(p + 6) = id;
}

// 读保持寄存器      （发送数据首地址，  功能码，   寄存器地址，寄存器数量，存放接受数据首地址）
void read_registers(uint8_t *p, int function, int addr, int nb, uint8_t *dest)
{
    int i;
    *(p + 5) = 6;              // 后面字节数
    *(p + 7) = (char)function; // 功能码
    *(p + 8) = addr >> 8;      // 寄存器高字节地址
    *(p + 9) = addr & 0xff;    // 寄存器低字节地址
    *(p + 10) = nb >> 8;       // 寄存器数量高位
    *(p + 11) = nb & 0xff;     // 寄存器数量低位

    send(sockfd, p, 12, 0); // 注意这里不能sizeof(p),p为指针

    recv(sockfd, dest, 64, 0); // 注意这里不能sizeof(dest),dest为指针
}

void write_coil(uint8_t *p, int function, int addr, int nb, uint8_t *dest)
{
    int i = 0;
    *(p + 5) = 6;              // 后面字节数
    *(p + 7) = (char)function; // 功能码
    *(p + 8) = addr >> 8;      // 线圈高位地址
    *(p + 9) = addr & 0xff;    // 线圈低位地址
    if (nb == 1)
        *(p + 10) = 0xff;
    else if (nb == 0)
        *(p + 10) = 0x00;
    *(p + 11) = 0x00;

    send(sockfd, p, 12, 0);

    recv(sockfd, dest, 64, 0);
}

// 读取输入寄存器函数
void read_input_registers(uint8_t *p, int function, int addr, int nb, uint8_t *dest)
{
    int i;
    *(p + 5) = 6;              // 后面字节数
    *(p + 7) = (char)function; // 功能码
    *(p + 8) = addr >> 8;      // 寄存器高字节地址
    *(p + 9) = addr & 0xff;    // 寄存器低字节地址
    *(p + 10) = nb >> 8;       // 寄存器数量高位
    *(p + 11) = nb & 0xff;     // 寄存器数量低位

    send(sockfd, p, 12, 0);    // 发送请求
    recv(sockfd, dest, 64, 0); // 接收响应
}

// 将十六进制字符串转换为十进制整数
long long hex_to_decimal(const char *hex_str)
{
    long long result = 0;
    size_t len = strlen(hex_str);

    // 跳过可能的 "0x" 或 "0X" 前缀
    if (len >= 2 && hex_str[0] == '0' && (hex_str[1] == 'x' || hex_str[1] == 'X'))
    {
        hex_str += 2;
        len -= 2;
    }

    for (size_t i = 0; i < len; i++)
    {
        char c = toupper(hex_str[i]);
        if (c >= '0' && c <= '9')
        {
            result = result * 16 + (c - '0');
        }
        else if (c >= 'A' && c <= 'F')
        {
            result = result * 16 + (c - 'A' + 10);
        }
        else
        {
            // 遇到非十六进制字符，返回 0
            return 0;
        }
    }
    return result;
}

// 定义读取线圈的函数
void read_coils(uint8_t *p, int function, int addr, int nb, uint8_t *dest)
{
    int i;
    *(p + 5) = 6;              // 后面字节数
    *(p + 7) = (char)function; // 功能码
    *(p + 8) = addr >> 8;      // 线圈高位地址
    *(p + 9) = addr & 0xff;    // 线圈低位地址
    *(p + 10) = nb >> 8;       // 线圈数量高位
    *(p + 11) = nb & 0xff;     // 线圈数量低位

    send(sockfd, p, 12, 0);
    recv(sockfd, dest, 64, 0);
}

int main(int argc, char const *argv[])
{
    struct sockaddr_in s;

    uint8_t data[12] = {0};
    uint8_t dest[64] = {0};
    int i;

    // 1.socket创建套接字
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0)
    {
        perror("socket err");
        return -1;
    }

    socklen_t len = sizeof(struct sockaddr_in);

    // 2.填充结构体
    s.sin_family = AF_INET;                          // 协议族
    s.sin_port = htons(atoi("502"));                 // htons:小端转大端 atoi:将数字字符串转换为数值
    s.sin_addr.s_addr = inet_addr("192.168.50.207"); // 字符串转点分十进制

    // 3.connect请求连接
    if (connect(sockfd, (struct sockaddr *)&s, len) < 0)
    {
        perror("connect err");
        return -1;
    }

    // 定义读取线圈的函数
    void read_coils(uint8_t *p, int function, int addr, int nb, uint8_t *dest)
    {
        int i;
        *(p + 5) = 6;              // 后面字节数
        *(p + 7) = (char)function; // 功能码
        *(p + 8) = addr >> 8;      // 线圈高位地址
        *(p + 9) = addr & 0xff;    // 线圈低位地址
        *(p + 10) = nb >> 8;       // 线圈数量高位
        *(p + 11) = nb & 0xff;     // 线圈数量低位

        send(sockfd, p, 12, 0);
        recv(sockfd, dest, 64, 0);
    }

    // 5.循环发送
    while (1)
    {
        // 设置从机ID
        set_slave_id(data, 102);
        printf("从机id: %d\n", 102);

        printf("开始读线圈\n");
        // 使用功能码 0x01 读取 1 个线圈状态
        read_coils(data, 0x01, 0, 1, dest); // 修改线圈数量为 1

        printf("recv coil data:");
        char coil_hex_str[128] = {0};
        // 解析响应数据并拼接成十六进制字符串
        for (int i = 0; i < dest[8]; i++)
        {
            printf("%#x  ", dest[9 + i]);
            sprintf(coil_hex_str + strlen(coil_hex_str), "%02x", dest[9 + i]);
        }
        printf("\n");
        // 将线圈十六进制数据转换为十进制并打印
        long long coil_decimal = hex_to_decimal(coil_hex_str);
        printf("线圈数据十进制值: %lld\n", coil_decimal);

        // 设置从机 ID 为 101 读取输入寄存器
        set_slave_id(data, 101);
        printf("从机id: %d，开始读输入寄存器\n", 101);
        // 使用功能码 0x04 读取 1 个输入寄存器
        read_input_registers(data, 0x04, 0, 1, dest); // 修改输入寄存器数量为 1

        printf("recv input register data:");
        char input_reg_hex_str[128] = {0};
        // 解析响应数据并拼接成十六进制字符串
        for (int i = 0; i < dest[8]; i++)
        {
            printf("%#x  ", dest[9 + i]);
            sprintf(input_reg_hex_str + strlen(input_reg_hex_str), "%02x", dest[9 + i]);
        }
        printf("\n");
        // 将输入寄存器十六进制数据转换为十进制并打印
        long long input_reg_decimal = hex_to_decimal(input_reg_hex_str);
        printf("输入寄存器数据十进制值: %lld\n", input_reg_decimal);

        sleep(1);
    }

    // 5.关闭套接字
    close(sockfd);
    return 0;
}
